/* Time-stamp: <2009-11-12 13:22:23 xinhaoyuan> */
/* EKOS Kernel implementation */
#define _EKOS_KERNEL_C_

#include <kernel/kernel.h>
#include <inc/elf.h>

typedef
int(*mmoveproc_t)(char *dest, char *base, size_t offset, size_t fillsize, size_t copysize);

/* 简易的载入 ELF 镜像的过程 */
static int
load_elf_image(struct elf_t *image, mmoveproc_t mmove)
{
     struct proghdr_t *ph, *eph;
	 int err = 0;
	 
     if (image->e_magic != ELF_MAGIC)
		  return -E_INVAL;

     ph  = (struct proghdr_t *)((char *)image + image->e_phoff);
     eph = ph + image->e_phnum;

     for ( ; ph != eph; ++ph)
     {
		  if (ph->p_type == ELF_PROG_LOAD)
		  {
			   if ((err = mmove((char *)ph->p_va, (char *)image,
								ph->p_offset, ph->p_memsz, ph->p_filesz))
				   != 0)
			   {
					break;
			   }
		  }
     }

	 return err;
}

/* 拷贝内存的过程, 需要处理填充的情况 */
int
my_move(char *dest, char *base, size_t offset, size_t fillsize, size_t copysize)
{
#ifdef K_INIT_DEBUG
     kprintf(" -- trying to move [%x, %x] to [%x, %x]\n",
			 base + offset, base + offset + copysize,
			 dest, dest + fillsize);
#endif

     base = base + offset; offset = 0;

     uintptr_t r = (uintptr_t)dest & (PAGE_SIZE - 1);     
     int err;

     uint32_t perm;
     
     if (r)
     {
		  r = PAGE_SIZE - r;
		  perm = PTE_W | PTE_U;
		  if ((err = fix_addr(dest, &perm, false)) != 0)
		  {
			   return err;
		  }
		       
		  if (r > copysize)
		  {
			   memmove(dest, base, copysize);
			   memset(dest, 0, r - copysize);
	       
		  }
		  else
		  {
			   memmove(dest, base, r);
		  }
	  
		  dest += r;
		  base += r;
		  offset += r;
     }
     
     while (offset < fillsize)
     {
		  perm = PTE_W | PTE_U;
		  if ((err = fix_addr(dest, &perm, false)) != 0)
		  {
			   return err;
		  }
	  
		  if (offset + PAGE_SIZE <=  copysize)
			   memmove(dest, base, PAGE_SIZE);
		  else if (offset < copysize)
		  {
			   memmove(dest, base, copysize - offset);
			   memset(dest + copysize - offset, 0, PAGE_SIZE - copysize + offset);
		  }
		  else memset(dest, 0, PAGE_SIZE);

		  dest += PAGE_SIZE;
		  base += PAGE_SIZE;
		  offset += PAGE_SIZE;
     }

     return 0;
}

void
kmain(void)
{	 
     if (user_init() != 0) return;

     extern char user_prog[], user_prog_end[];
	 int err = 0;

	 /* initial pcs */
	 physaddr_t naddr;
	 if ((err = frame_alloc(&naddr)) != 0) {
		  kprintf("Could not initialize PCS, err = %e, system panic\n", err);
		  monitor(NULL);
	 }
	 else
	 {
		  frame_inc_ref(naddr);
		  
		  volatile struct pcs_t *pcs = get_local_pcs();
		  vpt[PAGE_NUM(pcs)] = naddr | PTE_W | PTE_P;
		  tlbflush();
		  
		  pcs_init_struct(pcs);
	 }

	 /* load elf */
	 if (err == 0)
		  if ((err = load_elf_image((struct elf_t *)user_prog, my_move)) != 0)
		  {
			   kprintf("Could not load initial elf image, err = %e, system panic\n", err);
			   monitor(NULL);
		  }
		  
	 if (err == 0)
	 {
		  /* Set the irq0 freq */
		  timer_init(1000);
		  
		  kprintf("Now jump into userspace\n");
		  user_start((void *)((struct elf_t *)user_prog)->e_entry);
     
		  /* now system is running in user mode */
		  /* and the interrupt is enabled */
		  /* notice that this procedure should not return */
	 }
}
